//SPDX-FileCopyrightText: Copyright 2022-2024 深圳市同心圆网络有限公司
//SPDX-License-Identifier: GPL-3.0-only

package dao

import (
	"context"
	"encoding/json"
	"time"

	"github.com/redis/go-redis/extra/redisotel/v9"
	"github.com/redis/go-redis/v9"
)

const (
	_CACHE_PREFIX_SESS                    = "sess:"
	_CACHE_PREFIX_ADMIN_SESS              = "admsess:"
	_CACHE_PREFIX_NOTICE_KEY              = "nk:"
	_CACHE_PREFIX_ENTRY_EDIT              = "entryedit:"
	_CACHE_PREFIX_BOARD_NODE_EDIT         = "boardnodeedit:"
	_CACHE_PREFIX_APP_TOKEN               = "apptoken:"
	_CACHE_PREFIX_K8S_TOKEN               = "k8stoken:"
	_CACHE_PREFIX_USER_ONETIME_TOKEN      = "userott:"
	_CACHE_PREFIX_EXT_SERVER_JOIN_TOKEN   = "extjoin:"
	_CACHE_PREFIX_EXT_SERVER_ACCESS_TOKEN = "extaccess:"
)

type SessionDbItem struct {
	SessionId   string `json:"sessionId"`
	UserId      string `json:"userId"`
	UserName    string `json:"userName"`
	DisplayName string `json:"displayName"`
	LogoUri     string `json:"logUri"`
	UserType    uint32 `json:"userType"`
	TestAccount bool   `json:"testAccount"`

	//外部账号令牌
	ExtraAccessToken  string `json:"extraAccessToken"`
	ExtraRefreshToken string `json:"extraRefreshToken"`
	ExtraLastRefresh  int64  `json:"extraLastRefresh"`
}

type AdminSessionDbItem struct {
	AdminSessionId string `json:"adminSessionId"`
	UserName       string `json:"userName"`
	ToSignStr      string `json:"toSignStr"`
	AuthOk         bool   `json:"authOk"`
}

type EntryEditDbItem struct {
	ProjectId  string `json:"projectId"`
	EntryId    string `json:"entryId"`
	EditUserId string `json:"editUserId"`
}

type BoardNodeEditDbItem struct {
	ProjectId  string `json:"projectId"`
	BoardId    string `json:"boardId"`
	NodeId     string `json:"nodeId"`
	EditUserId string `json:"editUserId"`
}

type AppTokenDbItem struct {
	AppToken  string `json:"appToken"`
	ProjectId string `json:"projectId"`
	UserId    string `json:"userId"`
}

type K8sTokenDbItem struct {
	K8sToken     string `json:"k8sToken"`
	ProjectId    string `json:"projectId"`
	MemberUserId string `json:"memberUserId"`
	Admin        bool   `json:"admin"`
}

type ExtServerJoinTokenDbItem struct {
	JoinToken  string `json:"joinToken"`
	ProjectId  string `json:"projectId"`
	ServerType uint32 `json:"serverType"`
}

type ExtServerAccessTokenDbItem struct {
	AccessToken       string `json:"accessToken"`
	ProjectId         string `json:"projectId"`
	MemberUserId      string `json:"memberUserId"`
	MemberDisplayName string `json:"memberDisplayName"`
	MemberLogoUri     string `json:"memberLogoUri"`
	MemberIsAdmin     bool   `json:"memberIsAdmin"`
}

type _CacheDao struct {
	rdb     *redis.Client
	msgChan <-chan *redis.Message
}

func newCacheDao(rdb *redis.Client) (*_CacheDao, error) {
	err := redisotel.InstrumentTracing(rdb)
	if err != nil {
		return nil, err
	}
	err = rdb.ConfigSet(context.Background(), "notify-keyspace-events", "Ex").Err()
	if err != nil {
		return nil, err
	}
	msgChan := rdb.PSubscribe(context.Background(), "__key*__:*").Channel()
	return &_CacheDao{rdb: rdb, msgChan: msgChan}, nil
}

func (db *_CacheDao) GetMsgChan() <-chan *redis.Message {
	return db.msgChan
}

func (db *_CacheDao) SetSession(ctx context.Context, dbItem *SessionDbItem) error {
	data, err := json.Marshal(dbItem)
	if err != nil {
		return err
	}
	return db.rdb.SetEx(ctx, _CACHE_PREFIX_SESS+dbItem.SessionId, data, 15*time.Minute).Err()
}

func (db *_CacheDao) GetSession(ctx context.Context, sessionId string) (*SessionDbItem, bool) {
	data, err := db.rdb.Get(ctx, _CACHE_PREFIX_SESS+sessionId).Result()
	if err != nil {
		return nil, false
	}
	retItem := &SessionDbItem{}
	err = json.Unmarshal([]byte(data), retItem)
	if err != nil {
		return nil, false
	}
	return retItem, true
}

func (db *_CacheDao) RemoveSession(ctx context.Context, sessionId string) {
	db.rdb.Del(ctx, _CACHE_PREFIX_SESS+sessionId).Result()
}

func (db *_CacheDao) KeepSessionAlive(ctx context.Context, sessionId string) {
	_, ok := db.GetSession(ctx, sessionId)
	if ok {
		db.rdb.Expire(ctx, _CACHE_PREFIX_SESS+sessionId, 15*time.Minute).Result()
	}
}

func (db *_CacheDao) SetAdminSession(ctx context.Context, dbItem *AdminSessionDbItem) error {
	data, err := json.Marshal(dbItem)
	if err != nil {
		return err
	}
	return db.rdb.SetEx(ctx, _CACHE_PREFIX_ADMIN_SESS+dbItem.AdminSessionId, data, 3*time.Minute).Err()
}

func (db *_CacheDao) GetAdminSession(ctx context.Context, adminSessionId string) (*AdminSessionDbItem, bool) {
	data, err := db.rdb.Get(ctx, _CACHE_PREFIX_ADMIN_SESS+adminSessionId).Result()
	if err != nil {
		return nil, false
	}
	retItem := &AdminSessionDbItem{}
	err = json.Unmarshal([]byte(data), retItem)
	if err != nil {
		return nil, false
	}
	return retItem, true
}

func (db *_CacheDao) KeepAdminSessionAlive(ctx context.Context, adminSessionId string) {
	_, ok := db.GetAdminSession(ctx, adminSessionId)
	if ok {
		db.rdb.Expire(ctx, _CACHE_PREFIX_ADMIN_SESS+adminSessionId, 3*time.Minute).Result()
	}
}

func (db *_CacheDao) SetNoticeKey(ctx context.Context, channelPath, noticeKey string) error {
	return db.rdb.SetEx(ctx, _CACHE_PREFIX_NOTICE_KEY+channelPath, noticeKey, 7*24*time.Hour).Err()
}

func (db *_CacheDao) GetNoticeKey(ctx context.Context, channelPath string) (string, bool) {
	noticeKey, err := db.rdb.Get(ctx, _CACHE_PREFIX_NOTICE_KEY+channelPath).Result()
	if err != nil {
		return "", false
	}
	return noticeKey, true
}

func (db *_CacheDao) SetEntryEdit(ctx context.Context, dbItem *EntryEditDbItem) error {
	data, err := json.Marshal(dbItem)
	if err != nil {
		return err
	}
	return db.rdb.SetEx(ctx, _CACHE_PREFIX_ENTRY_EDIT+dbItem.EntryId, data, 1*time.Minute).Err()
}

func (db *_CacheDao) ExistEntryEdit(ctx context.Context, entryId string) (bool, error) {
	c, err := db.rdb.Exists(ctx, _CACHE_PREFIX_ENTRY_EDIT+entryId).Result()
	return c > 0, err
}

func (db *_CacheDao) GetEntryEdit(ctx context.Context, entryId string) (*EntryEditDbItem, bool) {
	data, err := db.rdb.Get(ctx, _CACHE_PREFIX_ENTRY_EDIT+entryId).Result()
	if err != nil {
		return nil, false
	}
	retItem := &EntryEditDbItem{}
	err = json.Unmarshal([]byte(data), retItem)
	if err != nil {
		return nil, false
	}
	return retItem, true
}

func (db *_CacheDao) KeepEntryEdit(ctx context.Context, entryId string) {
	ok, _ := db.ExistEntryEdit(ctx, entryId)
	if ok {
		db.rdb.Expire(ctx, _CACHE_PREFIX_ENTRY_EDIT+entryId, 1*time.Minute).Result()
	}
}

func (db *_CacheDao) RemoveEntryEdit(ctx context.Context, entryId string) {
	db.rdb.Del(ctx, _CACHE_PREFIX_ENTRY_EDIT+entryId).Result()
}

func (db *_CacheDao) SetAppToken(ctx context.Context, dbItem *AppTokenDbItem) error {
	data, err := json.Marshal(dbItem)
	if err != nil {
		return nil
	}
	return db.rdb.SetEx(ctx, _CACHE_PREFIX_APP_TOKEN+dbItem.AppToken, data, 1*time.Minute).Err()
}

func (db *_CacheDao) GetAppToken(ctx context.Context, appToken string) (*AppTokenDbItem, bool) {
	data, err := db.rdb.Get(ctx, _CACHE_PREFIX_APP_TOKEN+appToken).Result()
	if err != nil {
		return nil, false
	}
	retItem := &AppTokenDbItem{}
	err = json.Unmarshal([]byte(data), retItem)
	if err != nil {
		return nil, false
	}
	return retItem, true
}

func (db *_CacheDao) SetK8sToken(ctx context.Context, dbItem *K8sTokenDbItem) error {
	data, err := json.Marshal(dbItem)
	if err != nil {
		return nil
	}
	return db.rdb.SetEx(ctx, _CACHE_PREFIX_K8S_TOKEN+dbItem.K8sToken, data, 1*time.Minute).Err()
}

func (db *_CacheDao) GetK8sToken(ctx context.Context, k8sToken string) (*K8sTokenDbItem, bool) {
	data, err := db.rdb.Get(ctx, _CACHE_PREFIX_K8S_TOKEN+k8sToken).Result()
	if err != nil {
		return nil, false
	}
	retItem := &K8sTokenDbItem{}
	err = json.Unmarshal([]byte(data), retItem)
	if err != nil {
		return nil, false
	}
	return retItem, true
}

func (db *_CacheDao) RemoveK8sToken(ctx context.Context, k8sToken string) {
	db.rdb.Del(ctx, _CACHE_PREFIX_K8S_TOKEN+k8sToken).Result()
}

func (db *_CacheDao) SetBoardNodeEdit(ctx context.Context, dbItem *BoardNodeEditDbItem) error {
	data, err := json.Marshal(dbItem)
	if err != nil {
		return err
	}
	return db.rdb.SetEx(ctx, _CACHE_PREFIX_BOARD_NODE_EDIT+dbItem.NodeId, data, 1*time.Minute).Err()
}

func (db *_CacheDao) ExistBoardNodeEdit(ctx context.Context, nodeId string) (bool, error) {
	c, err := db.rdb.Exists(ctx, _CACHE_PREFIX_BOARD_NODE_EDIT+nodeId).Result()
	return c > 0, err
}

func (db *_CacheDao) GetBoardNodeEdit(ctx context.Context, nodeId string) (*BoardNodeEditDbItem, bool) {
	data, err := db.rdb.Get(ctx, _CACHE_PREFIX_BOARD_NODE_EDIT+nodeId).Result()
	if err != nil {
		return nil, false
	}
	retItem := &BoardNodeEditDbItem{}
	err = json.Unmarshal([]byte(data), retItem)
	if err != nil {
		return nil, false
	}
	return retItem, true
}

func (db *_CacheDao) KeepBoardNodeEdit(ctx context.Context, nodeId string) {
	ok, _ := db.ExistBoardNodeEdit(ctx, nodeId)
	if ok {
		db.rdb.Expire(ctx, _CACHE_PREFIX_BOARD_NODE_EDIT+nodeId, 1*time.Minute).Result()
	}
}

func (db *_CacheDao) RemoveBoardNodeEdit(ctx context.Context, nodeId string) {
	db.rdb.Del(ctx, _CACHE_PREFIX_BOARD_NODE_EDIT+nodeId).Result()
}

func (db *_CacheDao) SetUserOnetimeToken(ctx context.Context, token, userId string) error {
	return db.rdb.SetEx(ctx, _CACHE_PREFIX_USER_ONETIME_TOKEN+token, userId, 1*time.Minute).Err()
}

func (db *_CacheDao) GetUserOnetimeToken(ctx context.Context, token string) (string, bool) {
	data, err := db.rdb.Get(ctx, _CACHE_PREFIX_USER_ONETIME_TOKEN+token).Result()
	if err != nil {
		return "", false
	}
	return data, true
}

func (db *_CacheDao) RemoveUserOnetimeToken(ctx context.Context, token string) {
	db.rdb.Del(ctx, _CACHE_PREFIX_USER_ONETIME_TOKEN+token).Result()
}

func (db *_CacheDao) SetExtServerJoinToken(ctx context.Context, dbItem *ExtServerJoinTokenDbItem) error {
	data, err := json.Marshal(dbItem)
	if err != nil {
		return nil
	}
	return db.rdb.SetEx(ctx, _CACHE_PREFIX_EXT_SERVER_JOIN_TOKEN+dbItem.JoinToken, data, 5*time.Minute).Err()
}

func (db *_CacheDao) GetExtServerJoinToken(ctx context.Context, joinToken string) (*ExtServerJoinTokenDbItem, bool) {
	data, err := db.rdb.Get(ctx, _CACHE_PREFIX_EXT_SERVER_JOIN_TOKEN+joinToken).Result()
	if err != nil {
		return nil, false
	}
	retItem := &ExtServerJoinTokenDbItem{}
	err = json.Unmarshal([]byte(data), retItem)
	if err != nil {
		return nil, false
	}
	return retItem, true
}

func (db *_CacheDao) SetExtServerAccessToken(ctx context.Context, dbItem *ExtServerAccessTokenDbItem) error {
	data, err := json.Marshal(dbItem)
	if err != nil {
		return nil
	}
	return db.rdb.SetEx(ctx, _CACHE_PREFIX_EXT_SERVER_ACCESS_TOKEN+dbItem.AccessToken, data, 10*time.Second).Err()
}

func (db *_CacheDao) GetExtServerAccessToken(ctx context.Context, accessToken string) (*ExtServerAccessTokenDbItem, bool) {
	data, err := db.rdb.Get(ctx, _CACHE_PREFIX_EXT_SERVER_ACCESS_TOKEN+accessToken).Result()
	if err != nil {
		return nil, false
	}
	retItem := &ExtServerAccessTokenDbItem{}
	err = json.Unmarshal([]byte(data), retItem)
	if err != nil {
		return nil, false
	}
	return retItem, true
}
